#include <string.h>
#include <stdio.h>
#include "net.h"
#include "arp.h"
#include "ethernet.h"
/**
 * @brief 初始的arp包
 * 
 */
static const arp_pkt_t arp_init_pkt = {
    .hw_type16 = constswap16(ARP_HW_ETHER),
    .pro_type16 = constswap16(NET_PROTOCOL_IP),
    .hw_len = NET_MAC_LEN,
    .pro_len = NET_IP_LEN,
    .sender_ip = NET_IF_IP,
    .sender_mac = NET_IF_MAC,
    .target_mac = {0}};

/**
 * @brief arp地址转换表，<ip,mac>的容器
 * 
 */
map_t arp_table;

/**
 * @brief arp buffer，<ip,buf_t>的容器
 * 
 */
map_t arp_buf;

/**
 * @brief 打印一条arp表项
 * 
 * @param ip 表项的ip地址
 * @param mac 表项的mac地址
 * @param timestamp 表项的更新时间
 */
void arp_entry_print(void *ip, void *mac, time_t *timestamp)
{
    printf("%s | %s | %s\n", iptos(ip), mactos(mac), timetos(*timestamp));
}

/**
 * @brief 打印整个arp表
 * 
 */
void arp_print()
{
    printf("===ARP TABLE BEGIN===\n");
    map_foreach(&arp_table, arp_entry_print);
    printf("===ARP TABLE  END ===\n");
}

/**
 * @brief 发送一个arp请求
 * 
 * @param target_ip 想要知道的目标的ip地址
 */
void arp_req(uint8_t *target_ip)
{
    // 初始化arp报文
    buf_init(&txbuf, sizeof(arp_pkt_t));

    // 填写报文头部基本信息
    arp_pkt_t* arp = (arp_pkt_t *)txbuf.data;
    memcpy(arp, &arp_init_pkt, sizeof(arp_pkt_t));
    
    // 填ip
    memcpy(arp->target_ip, target_ip, NET_IP_LEN);
    
    // 填操作码
    arp->opcode16 = swap16(ARP_REQUEST);

    // 广播报文
    ethernet_out(&txbuf, ether_broadcast_mac, NET_PROTOCOL_ARP);
}

/**
 * @brief 发送一个arp响应
 * 
 * @param target_ip 目标ip地址
 * @param target_mac 目标mac地址
 */
void arp_resp(uint8_t *target_ip, uint8_t *target_mac)
{
    // 初始化arp报文
    buf_init(&txbuf, sizeof(arp_pkt_t));

    // 填写报文头部
    arp_pkt_t* arp = (arp_pkt_t *)txbuf.data;
    memcpy(arp, &arp_init_pkt, sizeof(arp_pkt_t));

    // 填ip和mac
    memcpy(arp->target_ip, target_ip, NET_IP_LEN);
    memcpy(arp->target_mac, target_mac, NET_MAC_LEN);

    // 填操作码
    arp->opcode16 = swap16(ARP_REPLY);    

    // 将报文向下传到数据链路层
    ethernet_out(&txbuf, target_mac, NET_PROTOCOL_ARP);
}

/**
 * @brief 处理一个收到的数据包 
 * 
 * @param buf 要处理的数据包
 * @param src_mac 源mac地址
 */
void arp_in(buf_t *buf, uint8_t *src_mac)
{
    // 如果数据长度小于arp头部长度 直接丢弃之
    if(buf->len < sizeof(arp_pkt_t))    return;

    // // 报文完整性检查
    arp_pkt_t *arp = (arp_pkt_t *)buf->data;
    if(arp->hw_type16 != constswap16(ARP_HW_ETHER))                                 return;
    if(arp->pro_type16 != constswap16(NET_PROTOCOL_IP))                             return;
    if(arp->hw_len != NET_MAC_LEN)                                                  return;
    if(arp->pro_len != NET_IP_LEN)                                                  return;
    if(arp->opcode16 != swap16(ARP_REPLY) && arp->opcode16 != swap16(ARP_REQUEST))  return;

    // 更新arp缓存表项
    map_set(&arp_table, arp->sender_ip, src_mac);

    // 找出之前待发送的包
    buf_t *ever_buf = map_get(&arp_buf, arp->sender_ip);
    
    // 如果还有因为之前没找到mac待发送的包，则将这个包发送出去，并将其从map中删除
    if(ever_buf != NULL){
        // 以什么协议呢？ 默认上层协议是ip，毕竟ICMP报文也是封装在IP报文里的
        ethernet_out(ever_buf, src_mac, NET_PROTOCOL_IP);
        map_delete(&arp_buf, arp->sender_ip);
    }
    // 如果接收报文的IP地址没有对应的arp_buf缓存
    else{
        // 如果自己是被请求的ip地址 应答一下
        if(arp->opcode16 == swap16(ARP_REQUEST) && memcmp(arp->target_ip, net_if_ip, NET_IP_LEN) == 0){
            arp_resp(arp->sender_ip, src_mac);
        }
    }
}

/**
 * @brief 处理一个要发送的数据包
 * 
 * @param buf 要处理的数据包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void arp_out(buf_t *buf, uint8_t *ip)
{
    // 在arp缓存表中查mac
    uint8_t* mac = map_get(&arp_table, ip);

    // 如果查到了mac 直接发送到以太网层即可 默认上层协议为ip
    if(mac != NULL)  return ethernet_out(buf,  mac, NET_PROTOCOL_IP);

    // 查询是否已经有查这个mac的arp包发送出去了
    buf_t *ever_buf = map_get(&arp_buf, ip);

    // 如果已经有这个包了，就不再发送请求了
    if(ever_buf != NULL) return;

    // 保存待发送的包并发送arp请求
    map_set(&arp_buf, ip, buf);
    arp_req(ip);
}

/**
 * @brief 初始化arp协议
 * 
 */
void arp_init()
{
    map_init(&arp_table, NET_IP_LEN, NET_MAC_LEN, 0, ARP_TIMEOUT_SEC, NULL);
    map_init(&arp_buf, NET_IP_LEN, sizeof(buf_t), 0, ARP_MIN_INTERVAL, buf_copy);
    net_add_protocol(NET_PROTOCOL_ARP, arp_in);
    arp_req(net_if_ip);
}